package it.avantsoft.ls.simulation;

import java.io.Serializable;

public class SimAppraisals implements Serializable {

    private static int MAXSLOTS = 10;
    private static int MAXBAND = 1000;

    private float upStream;
    private float upStreamStandardDeviation = 0;
    private long upTime;
    private int totalSlots;
    private int freeSlots;
    private float oldUpStreamAverage = 0;
    private float sumOfSquareValues = 0;
    private float sumOfValues = 0;
    private float upStreamAverage = 0;
    private int clientId;
    private int numberOfValutation = 0;
    private int avgWeight = 1;
    private int stdDevWeight = 1;
    private int slotWeight = 1;
    private int timeWeight = 1;
    private int numApprWeight = 1;
    private float currentTimeAppraisal = 0;
    private float currentNumApprAppraisal = 0;
    private float currentSlotAppraisal = 0;
    private float currentStdDevAppraisal = 0;
    private float currentAvgAppraisal = 0;
    private float currentAppraisal = 0;

    //rimossi upStreamStandardDeviation e upStreamAverage, vengono calcolati direttamente all'interno della classe

    /**
     * Costruttore per la classe SimAppraisals
     *
     * @param clientId id del client su cui si vogliono fare le valutazioni
     * @param time     il tempo in millisecondi dal 1 gennaio del 1970 (-_-)' alla creazione del peer.
     * @param slots    numero di slot del peer. Si suppone che tutti questi slot siano freeslots.
     */
    public SimAppraisals(int clientId, int slots, long time) {
        resetAppraisals();
        this.totalSlots = 0;
        this.freeSlots = slots;
        this.clientId = clientId;
        upTime = time;
    }

    public String toString() {
        return "SimAppraisals{" +
                "clientId=" + clientId +
                "upStream=" + upStream +
                ", upStreamStandardDeviation=" + upStreamStandardDeviation +
                ", upTime=" + upTime +
                ", totalSlots=" + totalSlots +
                ", freeslots=" + freeSlots +
                ", upStreamAverage=" + upStreamAverage +
                "}\n";
    }


    public int getClientId() {
        return clientId;
    }

    public void setClientId(int clientId) {
        this.clientId = clientId;
    }

    public float getUpStreamAverage() {
        return upStreamAverage;
    }

    public float getUpStream() {
        return upStream;
    }

    public void setUpStream(float upStream) {
        this.upStream = upStream;
    }

    public float getUpStreamStandardDeviation() {
        return upStreamStandardDeviation;
    }

    public long getUpTime() {
        return (System.currentTimeMillis() - upTime);
    }

    public void setUpTime(long upTime) {
        this.upTime = upTime;
    }

    public int getTotalSlots() {
        return totalSlots;
    }

    public void setTotalSlots(int totalSlots) {
        this.totalSlots = totalSlots;
    }

    public void setFreeSlots(int slots) {
        this.freeSlots = slots;
    }

    public void addUpStreamValue(float value) {
        numberOfValutation++;
        modAvgValue(value);
        modStdDeviationValue(value);
    }

    public int getNumberOfValutation() {
        return numberOfValutation;
    }

    public float getCurrentAppraisal() {
        evaluate();
        return currentAppraisal;
    }

    /**
     * Metodo per effettuare il reset delle valutazioni
     */
    public void resetAppraisals() {
        numberOfValutation = 0;
        upStreamAverage = 0;
        sumOfSquareValues = 0;
        sumOfValues = 0;
        upStreamStandardDeviation = 0;
    }

    /**
     * Aggiunge un valore alla deviazione standard della banda utilizzata.
     *
     * @param value valore della banda attuale utilizzata
     */
    private void modStdDeviationValue(float value) {
        float tmpVariance;
        if (numberOfValutation > 1) {
            sumOfSquareValues += value * value;
            sumOfValues += value;
            tmpVariance = (float) Math.sqrt(sumOfSquareValues - 2 * upStreamAverage * sumOfValues - numberOfValutation * upStreamAverage * upStreamAverage);
        } else {
            tmpVariance = 0;
            sumOfSquareValues += value * value;
            sumOfValues += value;
        }
        upStreamStandardDeviation = tmpVariance;
    }

    /**
     * Aggiunge un valore alla media dei valori della banda utilizzata
     *
     * @param value valore della banda attuale utilizzata
     */
    private void modAvgValue(float value) {
        float tmpAvg = upStreamAverage;
        if (numberOfValutation > 1) {
            tmpAvg *= (numberOfValutation - 1);
            tmpAvg += value;
            tmpAvg /= numberOfValutation;
        } else {
            tmpAvg = value;
        }
        oldUpStreamAverage = upStreamAverage;
        upStreamAverage = tmpAvg;
    }

    /**
     * Una serie di valori interi che definiscono i vari pesi da dare ai valori, per la valutazione finale
     *
     * @param average  peso da assegnare alla media
     * @param variance peso da assegnare alla deviazione standard
     * @param slots    peso da assegnare alla percentuale di slot utilizzati
     * @param numappr  peso da assegnare al numero di dati sul client considerati
     * @param time     peso da assegnare alla quantita' di tempo in cui il client e' rimasto up
     */

    public void changeWeightValues(int average, int variance, int slots, int numappr, int time) {
        avgWeight = average;
        stdDevWeight = variance;
        slotWeight = slots;
        timeWeight = time;
        numApprWeight = numappr;
    }

    /**
     * Calcolo delle valutazioni sulla deviazioni standard, sulla media e sugli slot.
     * Il metodo non tiene conto del tempo e del numero delle valutazioni, in quanto questi valori assumono un significato solo in un confronto tra due valutazioni.
     */

    private void evaluate() {

        // le valutazioni vengono calcolate separatamente, ciascuna e' trasformata in percentuale e moltiplicata per il proprio peso
        // La valutazione finale viene fornita dalla media dei tre valori, divisa per la somma dei pesi

        currentAvgAppraisal = upStreamAverage / MAXBAND * avgWeight;
        currentStdDevAppraisal = 1 - upStreamStandardDeviation / MAXBAND * stdDevWeight;
        currentSlotAppraisal = 1 - freeSlots / (totalSlots + freeSlots) * slotWeight;
        if (avgWeight + stdDevWeight + slotWeight > 0) {
            currentAppraisal = (currentAvgAppraisal + currentStdDevAppraisal + currentSlotAppraisal) / (avgWeight + stdDevWeight + slotWeight);
        } else {
            currentAppraisal = 0;
        }
    }

    /**
     * Questo metodo effettua il confronto tra due valutazioni di due client.
     * Per il calcolo vengono considerati anche il tempo in cui il client e' rimasto up e il numero di valutazioni.
     *
     * @param o Un oggetto {@link SimAppraisals}
     * @return 0 se le valutazioni sono uguali, 1 se la valutazione del client attuale e' maggiore di quella del client con cui si vuole comparare, -1 altrimenti
     * @throws ClassCastException L'eccezione viene lanciata se l'oggetto con cui si vogliono comparare le valutazioni non e' del tipo {@link SimAppraisals}
     */

    public int compareTo(Object o) throws ClassCastException {
        SimAppraisals tmp;
        float timeModifier = 0;
        float maxTime = 0;
        float numApprModifier = 0;
        float maxAppr = 0;
        float compclientAppraisal;
        float thisclientAppraisal;

        // Controllo se l'oggetto passato e' di tipo SimAppraisals
        if (!(o instanceof SimAppraisals)) {
            throw new ClassCastException();
        }
        tmp = (SimAppraisals) o;

        // Recupero le valutazioni parziali, basate solamente su media, deviazione standard e slot
        compclientAppraisal = tmp.getCurrentAppraisal();
        thisclientAppraisal = this.getCurrentAppraisal();

        // Recupero i tempi di vita e il numero di valutazioni dei due client
        timeModifier = tmp.getUpTime();
        currentTimeAppraisal = this.getUpTime();

        numApprModifier = tmp.getNumberOfValutation();
        currentNumApprAppraisal = this.getNumberOfValutation();


        maxTime = Math.max(timeModifier, currentTimeAppraisal);
        maxAppr = Math.max(numApprModifier, currentNumApprAppraisal);

        // Li trasformo in percentuale, l'uno relativamente all'altro
        timeModifier /= maxTime;
        currentTimeAppraisal /= maxTime;

        numApprModifier /= maxAppr;
        currentNumApprAppraisal /= maxAppr;

        // Modifico i valori in base al tempo e al numero di valutazioni
        compclientAppraisal = compclientAppraisal * (avgWeight + stdDevWeight + slotWeight + timeModifier * timeWeight + numApprModifier * numApprWeight) / (avgWeight + stdDevWeight + slotWeight + timeWeight + numApprWeight);
        thisclientAppraisal = thisclientAppraisal * (avgWeight + stdDevWeight + slotWeight + currentTimeAppraisal * timeWeight + currentNumApprAppraisal * numApprWeight) / (avgWeight + stdDevWeight + slotWeight + timeWeight + numApprWeight);

        if (thisclientAppraisal == compclientAppraisal) {
            return 0;
        } else if (thisclientAppraisal > compclientAppraisal) {
            return 1;
        }
        return -1;
    }
}
